﻿using System;
using System.Collections.Generic;
using AdaptiveAgents.Distributions;
using AdaptiveAgents.Agents;
using AdaptiveAgents.Loggers;
using AdaptiveAgents.Games;
using System.Data;
using AdaptiveAgents.Tools;

namespace AdaptiveAgents.Experiments
{
    /// <summary>
    /// </summary>
    class NAgentsExperiment : Experiment
    {
        //data members
        public List<double> EpsilonData { get; private set; }
        public List<double> CompetenceData { get; private set; }

        const int GAMES = 10; //number of games to run
        const int ROUNDS = 500; //number of round each game
        const int NUMBER_OF_AGENTS = 10; //number of round each game
        
        /// <summary>
        /// Constructor - 
        /// </summary>
        public NAgentsExperiment()
            : base(ROUNDS, NUMBER_OF_AGENTS, GAMES) { }

        //data members to Store the experiment data
        private DataTable dtGreedy; //for a run with Greedy agent
        private DataTable dtDiscover; //for a run with  agent
        private DataTable dtDiscoverWithoutCompetance; //for a run with  agent
        private DataTable dtOptimal; //for a run with  agent

        /// <summary>
        /// run the experiment
        /// </summary>
        public override void runExperiment()
        {

            EpsilonData = new List<double>();
            CompetenceData = new List<double>();
            //create a DataTable for MDPDiscover agent's data and set its rows

            dtGreedy = CreateDataTable("Greedy", NumAgents, ROUNDS);
            dtDiscover = CreateDataTable("Discover", NumAgents, ROUNDS);
            dtDiscoverWithoutCompetance = CreateDataTable("DiscoverWithoutCompetance", NumAgents, ROUNDS);
            dtOptimal = CreateDataTable("Optimal", NumAgents, ROUNDS);

            // read epsilon + competence from csv file
            DataTable dt = CSVprinter.ReadFromCSV("input.csv", ',');

            //run games
            for (int i = 0; i < GAMES; ++i)
            {
                // get competence + epsilon
                //CompetenceData = Generator.GetRandomNumbers(numAgents, true);
                //EpsilonData = Generator.GetRandomNumbers(numAgents, false);
                EpsilonData.Clear();
                CompetenceData.Clear();

                DataRow dr = dt.Rows[i];
                for(int j = 0; j< NumAgents; j++)
                {
                    EpsilonData.Add(Double.Parse((string)dr["Epsilon" + j]));
                    CompetenceData.Add(Double.Parse((string)dr["Competence" + j]));
                }
                
                

                //write to console to see progress
                if (i % 10 == 0)
                    System.Console.WriteLine(i);

                RunGame(new GreedyBuilder(), dtGreedy, i, GameType.MDPNew);

                RunGame(new MDPDiscoverBuilder(), dtDiscover, i, GameType.MDPNew);

                RunGame(new MDPDiscoverBuilder(), dtDiscoverWithoutCompetance, i, GameType.MDPWithoutUpdateCompetance);

                RunGame(new MDPOptimalBuilder(CompetenceData, EpsilonData), dtOptimal, i, GameType.MDPNew);

            }

            //create file names and write to the files
            String stamp = DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss");
            

            CSVprinter.WriteToCSV("GreedyExperiment_" + stamp + ".csv", dtGreedy);
            CSVprinter.WriteToCSV("DiscoverExperiment_" + stamp + ".csv", dtDiscover);
            CSVprinter.WriteToCSV("DiscoverWithoutCompetanceExperiment_" + stamp + ".csv", dtDiscoverWithoutCompetance);
            CSVprinter.WriteToCSV("OptimalExperiment_" + stamp + ".csv", dtOptimal);

           
        }


        private DataTable CreateDataTable(String title, int numberOfAgents, int rounds)
        {
            DataTable dt = new DataTable(title);
            DataColumn cln = new DataColumn("GameNum", typeof(double));
            dt.Columns.Add(cln);

            for (int i = 0; i < numberOfAgents; i++)
            {
                cln = new DataColumn("Competence" + i, typeof(double));
                dt.Columns.Add(cln);
            }

            for (int i = 0; i < numberOfAgents; i++)
            {
                cln = new DataColumn("Epsilon" + i, typeof(double));
                dt.Columns.Add(cln);
            }

            cln = new DataColumn("utility " + title, typeof(double));
            dt.Columns.Add(cln);

            cln = new DataColumn("sum", typeof(double));
            dt.Columns.Add(cln);

            //add rows for each round
            for (int i = 0; i < rounds; ++i)
            {
                cln = new DataColumn(i.ToString(), typeof(double));
                dt.Columns.Add("utility " + cln);
            }
            return dt;
        }

        /// <summary>
        /// Run a dingle game
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="dt"></param>
        /// <param name="gameNum"></param>
        protected void RunGame(AgentBuilder builder, DataTable dt, int gameNum, Games.GameType gameType)
        {
            // first let's set the environment
            Environment environment = new Environment();
            DataRow row = dt.NewRow();

            row["GameNum"] = gameNum;

            for (int j = 0; j < NumAgents; j++)
            {
                row["Epsilon" + j] = EpsilonData[j];
            }

            for (int j = 0; j < NumAgents; j++)
            {
                row["Competence" + j] = CompetenceData[j];
            }   

            //Create a new population of players
            //The agentType is MDPDiscovery so we create a MDPDiscovery agent and two normal agents
            List<Agent> agents = generatePlayers(environment, builder);

            for (int j = 0; j < NumAgents; j++)
            {
                Agent agent = agents[j];
                environment.addAgent(agent);
            }

            //create a game
            MDPGame game = (MDPGame)GameFactory.create(gameType, environment, ROUNDS);

            double utility = 0;

            //set the agents with their Competence
            for (int j = 0; j < NumAgents; j++)
            {
                environment.agents[j].Competence = new Blocks(CompetenceData[j]);
            }

            //enter to the game the row it needs to write the game data to
            game.Dr1 = row;

            //run the game and get back the utility of the game
            utility = game.start(_logger);

            //update utility
            row["utility " + dt.TableName] = utility;

            //total sum
            row["sum"] = game.GetAccumUtility();

            //add the game data to the DataTable
            dt.Rows.Add(row);
        }

        /// <summary>
        /// Generate the agents for the game
        /// </summary>
        /// <param name="env">the EnvironmentType of the game</param>
        /// <param name="builder">the builder for the first agent</param>
        /// <returns>a list of agents for the game</returns>
        protected List<Agent> generatePlayers(Environment env, Agents.AgentBuilder builder)
        {
            //create agents list
            List<Agent> retList = new List<Agent>();
            //create director
            AgentDirector director = new AgentDirector();
            director.Builder = builder;

          
            Agent ag;
            //construct the first agent according to the director
            director.construct(0, env, 0, _logger); 
            ag = director.getAgent();
            ag.ChangeStrategyPoint = 100;
            retList.Add(ag);

            //construct the 2nd and Nth agents as normal agents
            for (int i = 1; i < NumAgents; i++)
            {
                director.Builder = new Agents.NormalBuilder();
                director.construct(i, env, EpsilonData[i], _logger);
                retList.Add(director.getAgent());
            }
     
            return retList;
        }
    }
}
